class PerfSDK {
    constructor(options = {}) {
        if (!options.appid) throw new Error('appId is required')
        if (!options.reportUrl) throw new Error('reportUrl is required')

        const defaults = {
            appId: '',
            reportUrl: '',
            debug: true,
            context: {},
            // 报告批量与队列
            batchSize: 20,
            maxQueue: 20,
            // 开关
            collect: {
                vitals: true,
                longTask: true,
                resource: true,
                errors: true,
                networkFailures: true
            },
            whiteScreen: true
        }

        this.opts = this._deepMerge(defaults, options);

        this.started = false

        // Reporter
        this._queue = [];
        this._timer = null;
    }
    // =============== Public API ===============

    start() {
        if (this.started) return; this.started = true;
        const c = this.opts.collect || {};
        // 性能指标获取
        if (c.vitals) this._initVitals();
        // 长任务获取
        if (c.longTask) this._initLongTasks();
        // 页面资源获取
        if (c.resource) this._initResources();
        // 异常获取
        if (c.errors) this._initErrors();
        // 网络异常获取
        if (c.networkFailures) this._initNetworkFail();
        // 白屏检测
        if (this.opts.whiteScreen) this._scheduleWhiteScreen();

        this._onHidden(() => this._flush());
    }

    // =============== Core emit/observe ===============

    _emit(name, value, details) {
        const base = {
            name,
            value,
            ts: Date.now(),
            page: location.origin,
            path: location.pathname + location.search + location.hash,
            referrer: document.referrer || undefined,
            visibilityState: document.visibilityState,
            navType: this.navType,
            cid: this.cid,
            uid: this.opts.uid,
            sid: this.sid,
            appId: this.opts.appId,
            ua: navigator.userAgent,
            platform: navigator.platform,
            language: navigator.language,
            // network: this._getConnection(),
            details: Object.assign({}, details || {}, this.opts.context || {}),
        };
        this._push(base);
    }

    _push(ev) {
        this._queue.push(ev);
        if (this._queue.length > this.opts.maxQueue) this._queue.shift();
        if (this.opts.debug) try { console.debug('[PerfSDK] queued', ev.name, ev); } catch {}
        if (this._queue.length >= this.opts.batchSize) this._flush();
        if (!this._timer) this._timer = setTimeout(() => this._flush(), this.opts.flushInterval);
    }
    // 上送数据
    _flush() {
        if (this._timer) { clearTimeout(this._timer); this._timer = null; }
        if (!this._queue.length) return;
        const batch = this._queue.splice(0, this.opts.batchSize);
        const payload = JSON.stringify({ v: 1, t: Date.now(), data: batch });

        // sendBeacon 优先
        // try {
        // const blob = new Blob([payload], { type: 'application/json' });
        // if (navigator.sendBeacon && navigator.sendBeacon(this.opts.reportUrl, blob)) { this._backoff = 1000; return; }
        // } catch {}

        // // fetch 回退
        // fetch(this.opts.reportUrl, {
        //     method: 'POST',
        //     headers: { 'Content-Type': 'application/json' },
        //     keepalive: true,
        //     body: payload,
        // }).then(() => {
        //     this._backoff = 1000;
        // }).catch(() => {
        //     this._backoff = Math.min(this._backoff * 2, 30000);
        //     setTimeout(() => { this._queue = batch.concat(this._queue); this._flush(); }, this._backoff);
        // });
    }

    _observe(type, cb) {
        try {
            const po = new PerformanceObserver(list => list.getEntries().forEach(cb));
            po.observe({ type, buffered: true });
        } catch (error) {
            if (this.opts.debug) console.warn('[PerfSDK] observer unsupported:', type, e);
        }
    }


    // =============== Vitals ===============
    _initVitals() {
        // FP FCP
        this._observe('paint', e => {
            if (e.name === 'first-paint') this._emit('FP', e.startTime);
            if (e.name === 'first-contentful-paint')  this._emit('FCP', e.startTime);
        })
        // LCP
        this._observe('largest-contentful-paint', e => {
            // e.startTime：LCP 发生的时间（相对于页面加载起始点）。
            // e.size：最大内容元素的面积（像素）。
            // e.id：关联元素的 ID（如有）
            this._emit('LCP', e.startTime, { size: e.size, id: e.id });
        })

        // CLS（ 累积布局偏移）
        let cls = 0;
        // !e.hadRecentInput 排除输入框输入的影响
        this._observe('layout-shift', e => { if (!e.hadRecentInput) { cls += e.value; this._emit('CLS', cls); } });
        
        // FID 首次输入延迟
        // first-input：监听的性能条目类型（首次输入事件）。
        // e.startTime：用户交互发生的时间（如点击时间戳）。
        // e.processingStart：浏览器开始处理事件的时间戳。
        // delay：计算从交互到处理的延迟时间（processingStart - startTime）。
        // e.name：交互类型（如 'click'、'keydown'）。
        try { this._observe('first-input', e => {
            const delay = (e.processingStart || 0) - (e.startTime || 0);
            if (delay >= 0) this._emit('FID', delay, { name: e.name });
        }); } catch {}

        // INP（近似 via event entries）
        // PerformanceObserver：监听性能条目。
        // type: 'event'：捕获用户交互事件（点击、键盘输入等）。
        // durationThreshold: 40：仅记录耗时≥40ms的交互（避免噪音）。
        // entry.interactionId：标识唯一交互（浏览器自动生成）。
        // entry.duration：从交互开始到下一次绘制的总耗时。
        // entry.name：事件类型（如 'click'、'keydown'）。
        // buffered: true：获取历史条目（页面加载后初始化监听时有用）。
         try {
            (new PerformanceObserver(list => {
                list.getEntries().forEach(entry => {
                if (entry.interactionId && typeof entry.duration === 'number') {
                    this._emit('INP', entry.duration, { name: entry.name });
                }
                });
            })).observe({ type: 'event', buffered: true, durationThreshold: 40 });
        } catch {}

        // TTFB
        this._idle(() => {
            const nav = performance.getEntriesByType('navigation')[0]
            if (nav) {
                console.log('nav', nav)
                //secureConnectionStart 专门用于测量 SSL/TLS 握手阶段 的开始时间
                this._emit('TTFB', nav.responseStart, {
                    dns: nav.domainLookupEnd - nav.domainLookupStart,
                    connect: nav.connectEnd - nav.connectStart,
                    request: nav.responseStart - nav.requestStart
                })
            }
        })
    }

    // =============== Long Tasks ===============
    _initLongTasks() {
        // 监听和上报 Long Tasks（长任务） —— 浏览器主线程上运行时间超过 50 毫秒 的任务，它们是造成页面卡顿的常见原因。
        // longtask：监听的性能条目类型（长任务）。
        // e.duration：长任务的耗时（≥50ms）。
        // e.startTime：任务开始时间（相对于页面加载起点）。
        // e.name：任务类型（如 'script'、'layout'）。
        // e.attribution：任务归属信息（如哪个脚本/iframe 导致）。
        this._observe('longtask', e => {
            this._emit('LONG_TASK', e.duration, {
                start: e.startTime,
                name: e.name,
                attribution: (e.attribution || []).map(a => 
                    ({
                        name: a.name, 
                        entryType: a.entryType, // 标识性能条目的类型。
                        duration: a.duration, 
                        containerType: a.containerType, // 指示长任务发生的容器类型。iframe, window
                        containerSrc: a.containerSrc, // 记录容器的来源 URL（仅适用于 iframe/embed/object
                        lineNumber: a.lineNumber,  // 脚本行号（如果可用）
                        columnNumber: a.columnNumber
                    })),
            });
        });
    }

    // =============== Resources ===============
    _initResources() {
        this._onHidden(() => {
            const res = performance.getEntriesByType('resource');
            if (!res || !res.length) return;
            const top = res.slice().sort((a,b) => b.duration - a.duration).slice(0, 30)
            this._emit('RESOURCE', undefined, {
            top: top.map(r => ({
                    name: r.name, // 资源 URL
                    initiatorType: r.initiatorType, // 资源类型（script/link/img/xmlhttprequest等）
                    duration: r.duration, // 总耗时
                    transferSize: r.transferSize, // 传输大小（含HTTP头，单位：字节）
                    encodedBodySize: r.encodedBodySize, // 压缩后的响应体大小
                    decodedBodySize: r.decodedBodySize, // 解压后的实际大小（反映真实资源体积）
                    startTime: r.startTime, // 开始加载的时间戳
                })),
                count: res.length,
            });
        })
    }

    // =============== Errors ===============
    _initErrors() {
        window.addEventListener('error', event => {
            try {
                const t = event.target;
                // 资源加载异常
                if (t || (t instanceof HTMLScriptElement || t instanceof HTMLImageElement || t instanceof HTMLLinkElement)) {
                    const url = t.src || t.href;
                    this._emit('RESOURCE_ERROR', undefined, { tag: t.tagName, url }); return;
                }
                // js代码报错
                this._emit('ERROR', undefined, {
                    message: event.message,
                    filename: event.filename,
                    lineno: event.lineno,
                    colno: event.colno,
                    stack: event.error && event.error.stack,
                });
            } catch {}
        }, true)

        // 未捕获的promise异常
        window.addEventListener('unhandledrejection', event => {
            const r = event.reason;
            this._emit('UNHANDLEDREJECTION', undefined, 
                { 
                    message: (r && r.message) || String(r), 
                    stack: r && r.stack 
                });
        }, true)
    }

    // =============== Network failures ===============
    _initNetworkFail() {
        // fetch
        const origFetch = window.fetch
        if (origFetch) {
            window.fetch = (...args) => {
                const start = performance.now()
                return origFetch(...args).then(res => {
                    const duration = performance.now() - start
                    if (!res.ok) {
                        this._emit('FETCH_ERROR', undefined, { url: args[0], status: res.status, duration })
                    }
                }).catch(err => {
                    const duration = performance.now() - start;
                    this._emit('FETCH_ERROR', undefined, { url: args[0], message: err && err.message, duration });
                    throw err
                })
            }
        }

        // XHR
        const origOpen = XMLHttpRequest && XMLHttpRequest.prototype.open
        const origSend = XMLHttpRequest && XMLHttpRequest.prototype.send

        if (origOpen && origSend) {
            XMLHttpRequest.prototype.open = function(method, url) {
                this.__perf_url = url
                return origOpen.apply(this, arguments)
            }
            XMLHttpRequest.prototype.send = function() {
                // XMLHttpRequest (XHR) 请求完成事件 
                this.addEventListener('loadend', function() {
                    try {
                        const status = this.status; 
                        const url = this.__perf_url;
                        if (!(status >= 200 && status < 400)) {
                            window.__PerfSDKInstance && window.__PerfSDKInstance._emit('XHR_ERROR', undefined, { url, status });
                        }
                    } catch {}
                })
            }
        }
        // 暴露实例供 XHR 回调使用
        window.__PerfSDKInstance = this;
    }

    // =============== White screen ===============
    _scheduleWhiteScreen() {
        const cfg = {
            delay: 3000,
            threshold: 0.7,
            samplePoints: 9
        }
        const delay = cfg.delay
        const threshold = cfg.threshold
        const samplePoints = cfg.samplePoints
        window.addEventListener('load', () => {
            setTimeout(() => {
                requestAnimationFrame(() => {
                    if (this._detectWhiteScreen(samplePoints, threshold)) {
                        this._emit('WHITESCREEN');
                    }
                })
            }, delay);
        })
    }

    _detectWhiteScreen() {
        if (document.readyState !== 'complete' || !document.body) return true;
        const w = window.innerWidth, h = window.innerHeight;
        const pts = this._generateSamplingPoints(w, h, samplePoints);
        let empty = 0;
        for (const [x, y] of pts) {
            const els = document.elementsFromPoint(x, y).filter(el => !['HTML','BODY'].includes(el.tagName));
            if (!els.length) { empty++; continue; }
            const top = els[0];
            const rect = top.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) { empty++; continue; }
            const bg = getComputedStyle(top).backgroundColor;
            if (this._isTransparentOrWhite(bg) && !this._hasVisibleContent(top)) empty++;
        }
         return empty / pts.length > threshold;
    }
    _generateSamplingPoints(width, height, count) {
        const cols = Math.ceil(Math.sqrt(count)); 
        const rows = Math.ceil(count / cols);
        const pts = [];
        const mx = width * 0.1, my = height * 0.1;
        const sx = (width - mx * 2) / (cols - 1); 
        const sy = (height - my * 2) / (rows - 1);
        for (let i=0; i<rows; i++) { 
            for (let j=0; j<cols; j++) { 
                if (pts.length >= count) break; 
                pts.push([Math.round(mx + j*sx), 
                Math.round(my + i*sy)]); 
            } 
        }
        return pts;
    }

    _isTransparentOrWhite(color) {
        if (!color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)') return true;
        const m = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/); if (!m) return false;
        const r = +m[1], g = +m[2], b = +m[3]; return r > 240 && g > 240 && b > 240;
    }

    _hasVisibleContent(el) {
        const txt = (el.innerText || el.textContent || '').trim(); if (txt) return true;
        if (el instanceof HTMLImageElement && el.naturalWidth) return true;
        if (el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLInputElement) return true;
        return false;
    }

    _onHidden(cb) {
        const handler = () => { 
            try { cb(); } catch {} 
        }

        const vis = () => { 
            if (document.visibilityState === 'hidden') handler();
            document.removeEventListener('visibilitychange', vis, true); 
            window.removeEventListener('pagehide', handler, true);
        };

        document.addEventListener('visibilitychange', vis, true);
        window.addEventListener('pagehide', handler, true);
    }
    _deepMerge(base, ext) {
        // 处理基础对象/数组的拷贝
        const out = Array.isArray(base) ? base.slice() : Object.assign({}, base);
        
        // 遍历 ext 的所有属性
        Object.keys(ext || {}).forEach(k => {
            if (ext[k] && typeof ext[k] === 'object' && !Array.isArray(ext[k])) {
                // 递归合并对象
                out[k] = this._deepMerge(out[k] || {}, ext[k]);
            } else {
                // 直接赋值非对象值或数组
                out[k] = ext[k];
            }
        });
        
        return out;
    }
    _idle(cb){
        if ("requestIdleCallback" in window) {
            window.requestIdleCallback(cb, { timeout: 2000 })
        } else {
            setTimeout(cb, 0)
        }
    }
}